package com.dragonsen.log.helper

import com.dragonsen.baseplugin.AsmHelper
import com.dragonsen.baseplugin.asm.lambdaHelper
import com.dragonsen.log.LogConfig
import com.dragonsen.log.scan.DataClassManager
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.tree.*
import java.io.IOException

class LogClassNodeHelper : AsmHelper {

    private val classNodeMap = hashMapOf<String, ClassNode>()

    @Throws(IOException::class)
    override fun modifyClass(srcClass: ByteArray): ByteArray {
        val classNode = ClassNode(ASM5)
        val classReader = ClassReader(srcClass)
        //1 将读入的字节转为classNode
        classReader.accept(classNode, 0)
        classNodeMap[classNode.name] = classNode
        // 判断当前类是否实现了OnClickListener接口
        classNode.interfaces?.forEach {
            if (it == "android/view/View\$OnClickListener") {
                //整个类节点里的注解
                val field = classNode.getField()
                classNode.methods?.forEach { method ->
                    if (method.name == "onClick" && method.desc == "(Landroid/view/View;)V") {
//                        val field = method.getField()
                        insertTrack(classNode, method, field)
                    }
                }
            }
        }
        //lambada表达式基本都是
        classNode.lambdaHelper(true) {
            (it.name == "onClick" && it.desc.contains(")Landroid/view/View\$OnClickListener;"))
        }.forEach { method ->
            val field = classNode.getField()
            insertLambda(classNode, method, field)
        }
        val classWriter = ClassWriter(0)
        //3  将classNode转为字节数组
        classNode.accept(classWriter)
        return classWriter.toByteArray()
    }

    private fun insertLambda(node: ClassNode, method: MethodNode, field: FieldNode?) {
        // 判断方法名和方法描述
        val className = node.outerClass
        val parentNode = classNodeMap[className]
        // 根据outClassName 获取到外部类的Node
        val parentField = field ?: parentNode?.getField()
        val instructions = method.instructions
        instructions?.iterator()?.forEach {

            // 判断是不是代码的截止点
            if ((it.opcode >= Opcodes.IRETURN && it.opcode <= Opcodes.RETURN) || it.opcode == Opcodes.ATHROW) {
//                instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0))
//                instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0))

                var firstFlag = false

                //拿到方法中的参数并传给log
                kotlin.run {
                    if (!method.localVariables.isNullOrEmpty()) {
                        method.localVariables?.forEach { args ->
                            if (args.desc == "Landroid/view/View;") {
                                instructions.insertBefore(
                                    it,
                                    VarInsnNode(Opcodes.ALOAD, args.index)
                                )
                            }
                            //拿取到特定的
                            if (args.name == "DES") {
                                instructions.insertBefore(
                                    it,
                                    VarInsnNode(Opcodes.ALOAD, args.index)
                                )
                                firstFlag = true
                            }
                        }
                    }else
                    instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0))
                }


                // 注解的参数 获取到数据参数
                if (!firstFlag) {
                    if (parentField != null) {
                        parentField.apply {
                            instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0))
                            instructions.insertBefore(
                                it,
                                FieldInsnNode(
                                    Opcodes.GETFIELD,
                                    node.name,
                                    parentField.name,
                                    parentField.desc
                                )
                            )
                        }
                    } else {
                        instructions.insertBefore(it, LdcInsnNode("lambda注释显示1234"))
                    }
                }
                instructions.insertBefore(
                    it, MethodInsnNode(
                        INVOKESTATIC,
                        LogConfig.ByteCodeInjectClassName,
                        LogConfig.ByteCodeInjectFunctionName,
                        "(Ljava/lang/Object;Ljava/lang/Object;)V",
                        false
                    )
                )
            }
        }
    }

    private fun insertTrack(node: ClassNode, method: MethodNode, field: FieldNode?) {
        // 判断方法名和方法描述
        val className = node.outerClass
        val parentNode = classNodeMap[className]
        // 根据outClassName 获取到外部类的Node
        val parentField = field ?: parentNode?.getField()
        // 判断方法名和方法描述
        val instructions = method.instructions

        //传过来的数据
        var dataField: FieldInsnNode? = null

        instructions?.iterator()?.forEach {

            //TODO 要拿到本类的属性要拿到this
            if (it is FieldInsnNode && dataField == null) {
                if (it.opcode == GETFIELD && it.desc.length > 1) {
                    val classNames = it.desc.subSequence(1, it.desc.length - 1)
                    if (DataClassManager.INSTANCE.datList.contains(classNames)) {
                        dataField = it
                    }
                }
            }

            // 判断是不是代码的截止点
            if ((it.opcode >= Opcodes.IRETURN && it.opcode <= Opcodes.RETURN) || it.opcode == Opcodes.ATHROW) {

                //优先规定拿方法里面固定的属性 DES
                var firstFlag = false

                //拿到方法中的参数并传给log
                kotlin.run {
                    if (!method.localVariables.isNullOrEmpty()) {
                        method.localVariables?.forEach { args ->
                            if (args.desc == "Landroid/view/View;") {
                                instructions.insertBefore(
                                    it,
                                    VarInsnNode(Opcodes.ALOAD, args.index)
                                )
                            }
                            //拿取到特定的
                            if (args.name == "DES") {
                                instructions.insertBefore(
                                    it,
                                    VarInsnNode(Opcodes.ASTORE, args.index)
                                )
                                firstFlag = true
                            }
                        }
                    }
                    instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 1))
                }

                // 获取到数据参数
                if (!firstFlag) {
                    if (dataField != null) {
                        dataField?.let { dataField ->
                            instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0))
                            instructions.insertBefore(
                                it,
                                FieldInsnNode(
                                    dataField.opcode,
                                    dataField.owner,
                                    dataField.name,
                                    dataField.desc
                                )
                            )
                        }
                    } else if (parentField != null) {
                        parentField.apply {
                            instructions.insertBefore(it, VarInsnNode(Opcodes.ALOAD, 0))
                            instructions.insertBefore(
                                it,
                                FieldInsnNode(
                                    Opcodes.GETFIELD,
                                    node.name,
                                    parentField.name,
                                    parentField.desc
                                )
                            )
                        }
                    } else {
                        instructions.insertBefore(it, LdcInsnNode("没有注释显示1234"))
                    }
                }

                //执行log方法传入view
                instructions.insertBefore(
                    it, MethodInsnNode(
                        INVOKESTATIC,
                        LogConfig.ByteCodeInjectClassName,
                        LogConfig.ByteCodeInjectFunctionName,
                        "(Ljava/lang/Object;Ljava/lang/Object;)V",
                        false
                    )
                )
            }
        }
    }


    // 判断Field是否包含注解  比如Test注解  ClassNode下的
    private fun ClassNode.getField(): FieldNode? {
        return fields?.firstOrNull { field ->
            var hasAnnotation = false
            //多个的话只取一个
//            kotlin.run {
                field?.visibleAnnotations?.forEach { annotation ->
                    if (annotation.desc == "Lcom/example/reagent_management/ui/setting/Test;") {
                        hasAnnotation = true
                    }
                }
//            }
            hasAnnotation
        }
    }

    //获取method下的注解  拿不到
//    private fun MethodNode.getFiled():FieldNode?{
//        return visibleAnnotations?.forEach {
//            var hasAnnotation = false
//            annotation ->
//            if (annotation.desc == "Lcom/example/reagent_management/ui/setting/Test;") {
//                hasAnnotation = true
//            }
//        }
//    }

}


